﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Configuration;
using System.Data.SqlClient;
using System.Globalization;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;

namespace TeamDoer.Models
{
	public class EditProfileModel
	{
		[Required] public string Email{get;set;}

		[DisplayName("First name")]
		public string FirstName { get; set; }

		[DisplayName("Last name")]
		public string LastName { get; set; }

		[DisplayName("Twitter name")]
		public string TwitterName { get; set; }

		public EditProfileModel()
		{
		}

		public EditProfileModel(string username)
		{
			string email, firstname, lastname, twittername;
			AccountService srv = new AccountService();
			srv.GetUserInfoByUserName(username, out email, out firstname, out lastname, out twittername);
			Email = email;
			FirstName = firstname;
			LastName = lastname;
			TwitterName = twittername;
		}
	}

	[PropertiesMustMatch("NewPassword", "ConfirmPassword", ErrorMessage = "The new password and confirmation password do not match.")]
	public class ChangePasswordModel
	{
		[Required]
		[DataType(DataType.Password)]
		[DisplayName("Current password")]
		public string OldPassword { get; set; }

		[Required]
		[ValidatePasswordLength]
		[DataType(DataType.Password)]
		[DisplayName("New password")]
		public string NewPassword { get; set; }

		[Required]
		[DataType(DataType.Password)]
		[DisplayName("Confirm new password")]
		public string ConfirmPassword { get; set; }
	}

	public class LogOnModel
	{
		[Required]
		[DisplayName("User name")]
		public string UserName { get; set; }

		[Required]
		[DataType(DataType.Password)]
		[DisplayName("Password")]
		public string Password { get; set; }

		[DisplayName("Remember me?")]
		public bool RememberMe { get; set; }

		public string FacebookAppID { get { return ConfigurationManager.AppSettings["FacebookKey"]; } } 
	}

	public class RegisterModel
	{
		[Required]
		[DisplayName("User name")]
		public string UserName { get; set; }

		[Required]
		[DataType(DataType.EmailAddress)]
		[DisplayName("Email address")]
		public string Email { get; set; }

		[Required]
		[ValidatePasswordLength]
		[DataType(DataType.Password)]
		[DisplayName("Password")]
		public string Password { get; set; }

		[DisplayName("First name")]
		public string FirstName { get; set; }

		[DisplayName("Last name")]
		public string LastName { get; set; }
	}

	// The FormsAuthentication type is sealed and contains static members, so it is difficult to
	// unit test code that calls its members. The interface and helper class below demonstrate
	// how to create an abstract wrapper around such a type in order to make the AccountController
	// code unit testable.

	public class AccountService : BaseService
	{
		public const int MinPasswordLength = 6;

		public bool ValidateUser(string userName, string password)
		{
			if (String.IsNullOrWhiteSpace(userName)) throw new ArgumentException("Value cannot be null or empty.", "userName");
			if (String.IsNullOrWhiteSpace(password)) throw new ArgumentException("Value cannot be null or empty.", "password");
			if (password.Length < MinPasswordLength) throw new ArgumentException(string.Format("Password cannot be less than {0} chars.", MinPasswordLength), "password");

			_cmd.Parameters.Clear();
			_cmd.CommandText = "SELECT UserID FROM Users WHERE UserName=@UserName AND Password=@Password";
			_cmd.Parameters.AddWithValue("@UserName", userName);
			_cmd.Parameters.AddWithValue("@Password", Helpers.Utils.MD5Hash(password));
			_cn.Open();
			object res = _cmd.ExecuteScalar();
			_cn.Close();

			return (res!=null);
		}

		public int? GetUserIdByTwitterName(string twitterName)
		{
			_cmd.Parameters.Clear();
			_cmd.CommandText = "SELECT UserID FROM Users WHERE TwitterName=@TwitterName";
			_cmd.Parameters.AddWithValue("@TwitterName", twitterName);
			_cn.Open();
			object res = _cmd.ExecuteScalar();
			_cn.Close();

			return res as int?;
		}

		public int? GetUserIdByFacebookId(string facebookId, out string username)
		{
			int? retVal = null;
			username = null;

			_cmd.Parameters.Clear();
			_cmd.CommandText = "SELECT UserID, UserName FROM Users WHERE FacebookID=@FacebookID";
			_cmd.Parameters.AddWithValue("@FacebookID", facebookId);
			_cn.Open();
			SqlDataReader dr = _cmd.ExecuteReader();
			if (dr.Read())
			{
				retVal = Convert.ToInt32(dr["UserID"]);
				username = dr["UserName"].ToString();
			}
			_cn.Close();

			return retVal;
		}

		public MembershipCreateStatus CreateUser(string userName, string password, string email, string firstName, string lastName, string twitterUserName = "", string facebookId = "")
		{
			if (String.IsNullOrEmpty(userName)) throw new ArgumentException("Value cannot be null or empty.", "userName");
			if (String.IsNullOrEmpty(password)) throw new ArgumentException("Value cannot be null or empty.", "password");
			if (String.IsNullOrEmpty(email)) throw new ArgumentException("Value cannot be null or empty.", "email");

			if (GetUserIdByEmail(email).HasValue) return MembershipCreateStatus.DuplicateEmail;
			if (GetUserIdByUserName(userName).HasValue) return MembershipCreateStatus.DuplicateUserName;

			_cmd.Parameters.Clear();
			_cmd.CommandText = @"INSERT INTO Users (UserName, Email, Password, FirstName, LastName, TwitterName, FacebookID) 
				VALUES (@UserName, @Email, @Password, @FirstName, @LastName, @TwitterName, @FacebookID)";
			_cmd.Parameters.AddWithValue("@UserName", userName);
			_cmd.Parameters.AddWithValue("@Password", Helpers.Utils.MD5Hash(password));
			_cmd.Parameters.AddWithValue("@Email", email);
			_cmd.Parameters.AddWithValue("@FirstName", firstName ?? "");
			_cmd.Parameters.AddWithValue("@LastName", lastName ?? "");
			_cmd.Parameters.AddWithValue("@TwitterName", twitterUserName);
			_cmd.Parameters.AddWithValue("@FacebookID", facebookId);

			_cn.Open();
			_cmd.ExecuteNonQuery();
			_cn.Close();
			return MembershipCreateStatus.Success;
		}

		public bool ChangePassword(string userName, string oldPassword, string newPassword)
		{
			if (String.IsNullOrEmpty(userName)) throw new ArgumentException("Value cannot be null or empty.", "userName");
			if (String.IsNullOrEmpty(oldPassword)) throw new ArgumentException("Value cannot be null or empty.", "oldPassword");
			if (String.IsNullOrEmpty(newPassword)) throw new ArgumentException("Value cannot be null or empty.", "newPassword");

			if (!ValidateUser(userName, oldPassword)) return false;

			_cmd.Parameters.Clear();
			_cmd.CommandText = "UPDATE Users SET Password=@Password WHERE UserName=@UserName";
			_cmd.Parameters.AddWithValue("@UserName", userName);
			_cmd.Parameters.AddWithValue("@Password", Helpers.Utils.MD5Hash(newPassword));
			_cn.Open();
			_cmd.ExecuteNonQuery();
			_cn.Close();

			return true;
		}

		public bool UpdateUser(string userName, string email, string firstName, string lastName, string twitterName)
		{
			int? emailUser = GetUserIdByEmail(email);
			if (emailUser.HasValue && emailUser.Value != GetUserIdByUserName(userName).Value)
				return false;

			_cmd.Parameters.Clear();
			_cmd.CommandText = "UPDATE Users SET Email=@Email, FirstName=@FirstName, LastName=@LastName, TwitterName=@TwitterName WHERE UserName=@UserName";
			_cmd.Parameters.AddWithValue("@UserName", userName);
			_cmd.Parameters.AddWithValue("@Email", email);
			_cmd.Parameters.AddWithValue("@FirstName", firstName ?? "");
			_cmd.Parameters.AddWithValue("@LastName", lastName ?? "");
			_cmd.Parameters.AddWithValue("@TwitterName", twitterName ?? "");
			_cn.Open();
			_cmd.ExecuteNonQuery();
			_cn.Close();

			return true;
		}

		public bool SetUserFacebookId(int userId, string facebookId)
		{
			string tmp;
			int? fbUser = GetUserIdByFacebookId(facebookId, out tmp);
			if (fbUser.HasValue && fbUser.Value != userId)
				return false;

			_cmd.Parameters.Clear();
			_cmd.CommandText = "UPDATE Users SET FacebookID=@FacebookID WHERE UserID=@UserID";
			_cmd.Parameters.AddWithValue("@UserID", userId);
			_cmd.Parameters.AddWithValue("@FacebookID", facebookId);
			_cn.Open();
			_cmd.ExecuteNonQuery();
			_cn.Close();

			return true;
		}

		public int? GetUserIdByUserName(string username)
		{
			_cmd.Parameters.Clear();
			_cmd.CommandText = "SELECT UserID FROM Users WHERE UserName=@UserName";
			_cmd.Parameters.AddWithValue("@UserName", username);
			_cn.Open();
			object res = _cmd.ExecuteScalar();
			_cn.Close();

			return res as int?;
		}

		public void GetUserInfoByUserName(string username, out string email, out string firstName, out string lastName, out string twitterName)
		{
			email = firstName = lastName = twitterName = "";
			_cmd.Parameters.Clear();
			_cmd.CommandText = "SELECT Email, FirstName, LastName, TwitterName FROM Users WHERE UserName=@UserName";
			_cmd.Parameters.AddWithValue("@UserName", username);
			_cn.Open();
			SqlDataReader dr = _cmd.ExecuteReader();
			if (dr.Read())
			{
				email = dr["Email"].ToString();
				firstName = dr["FirstName"].ToString();
				lastName = dr["LastName"].ToString();
				twitterName = dr["TwitterName"].ToString();
			}
			dr.Close();
			_cn.Close();
		}

		public int? GetUserIdByEmail(string email)
		{
			_cmd.Parameters.Clear();
			_cmd.CommandText = "SELECT UserID FROM Users WHERE Email=@Email";
			_cmd.Parameters.AddWithValue("@Email", email);
			_cn.Open();
			object res = _cmd.ExecuteScalar();
			_cn.Close();

			return res as int?;
		}

		public void GetUserNameById(int userId, out string username, out string firstName, out string lastName)
		{
			username = firstName = lastName = "";
			_cmd.Parameters.Clear();
			_cmd.CommandText = "SELECT UserName, FirstName, LastName FROM Users WHERE UserID=@UserID";
			_cmd.Parameters.AddWithValue("@UserID", userId);
			_cn.Open();
			SqlDataReader dr = _cmd.ExecuteReader();
			if (dr.Read())
			{
				username = dr["UserName"].ToString();
				firstName = dr["FirstName"].ToString();
				lastName = dr["LastName"].ToString();
			}
			dr.Close();
			_cn.Close();
		}
		public string GetUserNameById(int userId)
		{
			string username = "", tmp1 = "", tmp2 = "";
			GetUserNameById(userId, out username, out tmp1, out tmp2);
			return username;
		}
	}

	public interface IFormsAuthenticationService
	{
		void SignIn(string userName, bool createPersistentCookie);
		void SignOut();
	}

	public class FormsAuthenticationService : IFormsAuthenticationService
	{
		public void SignIn(string userName, bool createPersistentCookie)
		{
			if (String.IsNullOrEmpty(userName)) throw new ArgumentException("Value cannot be null or empty.", "userName");

			FormsAuthentication.SetAuthCookie(userName, createPersistentCookie);
		}

		public void SignOut()
		{
			FormsAuthentication.SignOut();
		}
	}
	
	public static class AccountValidation
	{
		public static string ErrorCodeToString(MembershipCreateStatus createStatus)
		{
			// See http://go.microsoft.com/fwlink/?LinkID=177550 for
			// a full list of status codes.
			switch (createStatus)
			{
				case MembershipCreateStatus.DuplicateUserName:
					return "Username already exists. Please enter a different user name.";

				case MembershipCreateStatus.DuplicateEmail:
					return "A username for that e-mail address already exists. Please enter a different e-mail address.";

				case MembershipCreateStatus.InvalidPassword:
					return "The password provided is invalid. Please enter a valid password value.";

				case MembershipCreateStatus.InvalidEmail:
					return "The e-mail address provided is invalid. Please check the value and try again.";

				case MembershipCreateStatus.InvalidAnswer:
					return "The password retrieval answer provided is invalid. Please check the value and try again.";

				case MembershipCreateStatus.InvalidQuestion:
					return "The password retrieval question provided is invalid. Please check the value and try again.";

				case MembershipCreateStatus.InvalidUserName:
					return "The user name provided is invalid. Please check the value and try again.";

				case MembershipCreateStatus.ProviderError:
					return "The authentication provider returned an error. Please verify your entry and try again. If the problem persists, please contact your system administrator.";

				case MembershipCreateStatus.UserRejected:
					return "The user creation request has been canceled. Please verify your entry and try again. If the problem persists, please contact your system administrator.";

				default:
					return "An unknown error occurred. Please verify your entry and try again. If the problem persists, please contact your system administrator.";
			}
		}
	}

	[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
	public sealed class PropertiesMustMatchAttribute : ValidationAttribute
	{
		private const string _defaultErrorMessage = "'{0}' and '{1}' do not match.";
		private readonly object _typeId = new object();

		public PropertiesMustMatchAttribute(string originalProperty, string confirmProperty)
			: base(_defaultErrorMessage)
		{
			OriginalProperty = originalProperty;
			ConfirmProperty = confirmProperty;
		}

		public string ConfirmProperty { get; private set; }
		public string OriginalProperty { get; private set; }

		public override object TypeId
		{
			get
			{
				return _typeId;
			}
		}

		public override string FormatErrorMessage(string name)
		{
			return String.Format(CultureInfo.CurrentUICulture, ErrorMessageString,
				OriginalProperty, ConfirmProperty);
		}

		public override bool IsValid(object value)
		{
			PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(value);
			object originalValue = properties.Find(OriginalProperty, true /* ignoreCase */).GetValue(value);
			object confirmValue = properties.Find(ConfirmProperty, true /* ignoreCase */).GetValue(value);
			return Object.Equals(originalValue, confirmValue);
		}
	}

	[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
	public sealed class ValidatePasswordLengthAttribute : ValidationAttribute
	{
		private const string _defaultErrorMessage = "'{0}' must be at least {1} characters long.";
		private readonly int _minCharacters = AccountService.MinPasswordLength;

		public ValidatePasswordLengthAttribute()
			: base(_defaultErrorMessage)
		{
		}

		public override string FormatErrorMessage(string name)
		{
			return String.Format(CultureInfo.CurrentUICulture, ErrorMessageString,
				name, _minCharacters);
		}

		public override bool IsValid(object value)
		{
			string valueAsString = value as string;
			return (valueAsString != null && valueAsString.Length >= _minCharacters);
		}
	}
}
